package tcln.gxtx.client.utils.collection;

import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import tcln.gxtx.client.utils.assertion.GwtAssert;
import tcln.gxtx.client.utils.lang.GwtObjectUtils;

import com.extjs.gxt.ui.client.core.FastMap;

/**
 * Utility class for collections in GWT client-side.
 * 
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
public final class GwtCollectionUtils {

    /**
     * Null save check if a collection is empty.
     * 
     * @param c
     *            Is the given collection
     * @return Returns true if the given collection is null or empty.
     */
    public static boolean isEmpty(Collection<?> c) {
        return c == null || c.isEmpty();
    }

    /**
     * Null save check if a map is empty.
     * 
     * @param m
     * @return
     */
    public static boolean isEmpty(Map<?, ?> m) {
        return m == null || m.isEmpty();
    }

    /**
     * Checks if given array is <code>null</code> or contains no element.
     * 
     * @param a
     *            Array to check.
     * @return <code>true</code> if this array is <code>null</code> or contains no element.
     */
    public static boolean isEmpty(Object[] a) {
        return a == null || a.length == 0;
    }

    /**
     * Creates a map of (string, object) pairs from given key/value pairs.
     * <p>
     * Example: declaration
     * 
     * <pre>
     * Map<String, Object> m = createMapFromPairs("key1", Integer.MAX_VALUE, "key2, Double.POSITIVE_INFINITY);
     * </pre>
     * 
     * .. means:
     * 
     * <pre>
     * (m.get("key1") == Integer.MAX_VALUE) == true;
     * (m.get("key2") == Double.POSITIVE_INFINITY) == true;
     * </pre>
     * 
     * @param pairs
     * @return
     */
    public static Map<String, Object> createMapFromPairs(Object... pairs) {
        if (isEmpty(pairs)) {
            return new FastMap<Object>();
        }

        GwtAssert.STATE.isTrue((pairs.length % 2 == 0),
                "''pairs'' must have an even number of elements. Actual size = {0}", pairs.length);

        Map<String, Object> resultMap = new FastOrderedMap<Object>();
        for (int i = 0; i < pairs.length - 1; i += 2) {
            Object key = pairs[i];
            Object value = pairs[i + 1];

            if (key instanceof String) {
                resultMap.put((String) key, value);
            } else {
                resultMap.put(String.valueOf(key), value);
            }
        }

        return resultMap;
    }

    /**
     * Returns the index of given element in given source list via a optional custom comparator. If this comparator is
     * <code>null</code>, this method behaves like {@link List#indexOf(Object)}.
     * 
     * @param <T>
     * @param sourceList
     *            Source list to be checked.
     * @param element
     *            Element to check.
     * @param comparator
     *            A custom comparator for equal comparison between given element and each element in given source list.
     * @return <b>-1</b> if source list is empty or no element is equal to given element. Otherwise, returns index of
     *         given element in this list.
     */
    public static <T> int indexOf(List<T> sourceList, T element, Comparator<T> comparator) {
        if (isEmpty(sourceList)) {
            return -1;
        }

        if (comparator == null) {
            return sourceList.indexOf(element);
        } else {
            for (int i = 0; i < sourceList.size(); i++) {
                if (GwtObjectUtils.equals(sourceList.get(i), element, comparator)) {
                    return i;
                }
            }
            return -1;
        }
    }

    /**
     * Returns the size of given collection.
     * 
     * @param c
     *            Collection to get size.
     * @return {@link Collection#size()} or 0 if given collection is <code>null</code>.
     */
    public static int size(Collection<?> c) {
        return c != null ? c.size() : 0;
    }

    /**
     * Returns the size of given map.
     * 
     * @param m
     *            Map to get size.
     * @return {@link Map#size()} or 0 if given map is <code>null</code>.
     */
    public static int size(Map<?, ?> m) {
        return m != null ? m.size() : 0;
    }
}
